package com.cmes.system.core.service;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.cmes.core.constant.enums.EnableStatus;
import com.cmes.core.utils.CollectionUtils;
import com.cmes.core.utils.DateUtils;
import com.cmes.core.utils.Preconditions;
import com.cmes.core.utils.StringUtils;
import com.cmes.jdbc.service.GenericService;
import com.cmes.system.core.constant.Constants;
import com.cmes.system.core.constant.ErrorCode;
import com.cmes.system.core.entity.UserEntity;
import com.cmes.system.core.entity.UserRoleEntity;
import com.cmes.system.core.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 用户
 *
 * @author Bruce.Gong
 * @since 1.0.0-SNAPSHOT
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class UserService extends GenericService<UserRepository, UserEntity> {

    private final UserRoleService userRoleService;
    private final UserDeptService userDeptService;
    private final ConfigService configService;

    /**
     * 密码加密
     */
    private String password(String password, String salt) {
        return DigestUtils.sha256Hex(password + salt);
    }

    private String initPassword(String salt) {
        String password = configService.getValueByCode(Constants.CONFIG_DEFAULT_PASSWORD, Constants.DEFAULT_PASSWORD);
        return password(DigestUtils.md5Hex(password), salt);
    }

    // region 页面增删查改

    /**
     * 新增校验
     *
     * @param entity 新增对象
     */
    public void canAdd(UserEntity entity) {
        checkUsername(entity.getUsername());
        if (StringUtils.isNotEmpty(entity.getMobile())) {
            checkMobile(entity.getMobile());
        }
    }

    /**
     * 新增用户
     *
     * @param entity  用户信息
     * @param deptIds 部门信息
     * @param roleIds 角色信息
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void addUser(UserEntity entity, List<Long> deptIds, List<Long> roleIds) {
        entity.setSys(false);
        entity.setStatus(EnableStatus.ENABLE.getCode());
        entity.setSalt(StringUtils.substring(StringUtils.uuid(), 0, 8));
        // 设置默认密码
        entity.setPassword(initPassword(entity.getSalt()));
        insert(entity);
        // 分配部门
        allocateDept(entity.getId(), deptIds);
        // 分配角色
        allocateRole(entity.getId(), roleIds);
    }

    /**
     * 检查用户是否可修改
     *
     * @param entity 用户
     */
    public void canUpdate(UserEntity entity) {
        UserEntity db = checkActive(entity.getId());
        // 手机号发生变化，保证唯一
        boolean mobileChanged = !Objects.equals(db.getMobile(), entity.getMobile());
        if (mobileChanged && StringUtils.isNotEmpty(entity.getMobile())) {
            checkMobile(entity.getMobile());
        }
    }

    /**
     * 修改用户信息
     *
     * @param entity  用户信息
     * @param deptIds 部门
     * @param roleIds 角色
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void updateUser(UserEntity entity, List<Long> deptIds, List<Long> roleIds) {
        update(entity);
        // 调整部门
        updateDept(entity.getId(), deptIds);
        // 调整角色
        updateRole(entity.getId(), roleIds);
    }

    /**
     * 判断是否可删除
     *
     * @param ids 用户ID
     */
    public void canDelete(List<Long> ids) {
        List<UserEntity> dbs = checkExists(ids);
        checkSystem(dbs, "删除");
    }

    /**
     * 删除用户
     *
     * @param userIds 用户ID
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void deleteUser(List<Long> userIds) {
        deleteBatch(userIds);
        // 删除部门关系
        userDeptService.deleteByUserIds(userIds);
        // 删除角色关系
        userRoleService.deleteByUserIds(userIds);
    }

    /**
     * 检查是否可停用
     *
     * @param ids 用户ID
     */
    public void canDisable(List<Long> ids) {
        List<UserEntity> dbs = checkExists(ids);
        checkSystem(dbs, "冻结");
        checkStatus(dbs, EnableStatus.ENABLE, ErrorCode.USERS_DISABLED);
    }

    /**
     * 冻结用户
     *
     * @param ids 用户ID
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void disable(List<Long> ids) {
        update(Wrappers.<UserEntity>lambdaUpdate()
                .set(UserEntity::getStatus, EnableStatus.DISABLE.getCode())
                .in(UserEntity::getId, ids)
                .eq(UserEntity::getStatus, EnableStatus.ENABLE.getCode()));
    }

    /**
     * 检查是否可启用
     *
     * @param ids 用户ID
     */
    public void canEnable(List<Long> ids) {
        List<UserEntity> dbs = checkExists(ids);
        checkStatus(dbs, EnableStatus.DISABLE, ErrorCode.USERS_ENABLE);
    }

    /**
     * 解冻用户
     *
     * @param ids 用户ID
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void enable(List<Long> ids) {
        update(Wrappers.<UserEntity>lambdaUpdate()
                .set(UserEntity::getStatus, EnableStatus.ENABLE.getCode())
                .in(UserEntity::getId, ids)
                .eq(UserEntity::getStatus, EnableStatus.DISABLE.getCode()));
    }

    private UserEntity checkExists(Long id) {
        UserEntity db = getById(id);
        Preconditions.isNull(db, ErrorCode.USER_NOT_EXISTS);
        return db;
    }

    private List<UserEntity> checkExists(List<Long> ids) {
        List<UserEntity> db = listByIds(ids);
        Preconditions.isNull(db, ErrorCode.USER_NOT_EXISTS);
        return db;
    }

    /**
     * 判断是否可修改
     *
     * @param id 用户ID
     * @return 用户信息
     */
    private UserEntity checkActive(Long id) {
        UserEntity db = checkExists(id);
        Preconditions.check(() -> EnableStatus.DISABLE.match(db.getStatus()), ErrorCode.USER_DISABLED);
        return db;
    }

    /**
     * 分配部门
     *
     * @param userId  用户ID
     * @param deptIds 角色ID
     */
    private void allocateDept(Long userId, List<Long> deptIds) {
        if (CollectionUtils.isNotEmpty(deptIds)) {
            // TODO checkDeptExist();
            userDeptService.add(userId, deptIds);
        }
    }

    /**
     * 调整部门
     *
     * @param userId  用户ID
     * @param deptIds 部门ID
     */
    private void updateDept(Long userId, List<Long> deptIds) {
        userDeptService.deleteByUserId(userId);
        if (CollectionUtils.isNotEmpty(deptIds)) {
            allocateDept(userId, deptIds);
        }
    }

    /**
     * 分配角色
     *
     * @param userId  用户ID
     * @param roleIds 角色ID
     */
    private void allocateRole(Long userId, List<Long> roleIds) {
        if (CollectionUtils.isNotEmpty(roleIds)) {
            // TODO checkRoleExist()
            userRoleService.add(userId, roleIds);
        }
    }

    /**
     * 调整角色
     *
     * @param userId  用户ID
     * @param roleIds 角色ID
     */
    private void updateRole(Long userId, List<Long> roleIds) {
        userRoleService.deleteByUserId(userId);
        if (CollectionUtils.isNotEmpty(roleIds)) {
            allocateRole(userId, roleIds);
        }
    }

    /**
     * 校验用户名是否唯一
     *
     * @param username 登录名
     */
    private void checkUsername(String username) {
        Preconditions.check(() -> exists(Wrappers.<UserEntity>lambdaQuery()
                .eq(UserEntity::getUsername, username)), ErrorCode.USER_NAME_EXISTS);
    }

    /**
     * 校验手机号是否唯一
     *
     * @param mobile 手机号
     */
    private void checkMobile(String mobile) {
        Preconditions.check(() -> exists(Wrappers.<UserEntity>lambdaQuery()
                .eq(UserEntity::getMobile, mobile)), ErrorCode.USER_MOBILE_EXISTS);
    }

    /**
     * 系统用户不能删除或者作废
     *
     * @param dbs 用户列表
     * @param operate 操作
     */
    private void checkSystem(List<UserEntity> dbs, String operate) {
        List<String> sysCodes = dbs.stream()
                .filter(v -> Boolean.TRUE.equals(v.getSys()))
                .map(UserEntity::getUsername)
                .collect(Collectors.toList());
        Preconditions.notEmpty(sysCodes, ErrorCode.USER_IS_SYS, sysCodes, operate);
    }

    /**
     * 校验状态
     *
     * @param dbs 用户列表
     * @param status 期望的状态
     * @param errorCode 异常码
     */
    private void checkStatus(List<UserEntity> dbs, EnableStatus status, ErrorCode errorCode) {
        Set<String> users = dbs.stream()
                .filter(v -> !status.match(v.getStatus()))
                .map(UserEntity::getUsername)
                .collect(Collectors.toSet());
        Preconditions.notEmpty(users, errorCode, users);
    }

    // endregion

    // region 登录

    public UserEntity loginByUsername(String username) {
        UserEntity db = getOne(Wrappers.<UserEntity>lambdaQuery()
                .select(UserEntity::getId, UserEntity::getUsername,
                        UserEntity::getPassword, UserEntity::getSalt,
                        UserEntity::getNickname, UserEntity::getGender,
                        UserEntity::getAvatar, UserEntity::getStatus)
                .eq(UserEntity::getUsername, username));
        if (db != null) {
            Preconditions.check(() -> EnableStatus.DISABLE.match(db.getStatus()), ErrorCode.USER_DISABLED);
        }
        return db;
    }

    public Set<Long> authRoleIds(Long userId) {
        return userRoleService.listDistinctObjs(Wrappers.<UserRoleEntity>lambdaQuery()
                .select(UserRoleEntity::getRoleId)
                .eq(UserRoleEntity::getUserId, userId), UserRoleEntity::getRoleId);
    }

    // endregion

    // region 用户中心操作

    public void canUpdateProfile(UserEntity entity) {
        canUpdate(entity);
    }

    public void updatePassword(Long id, String oldPwd, String newPwd) {
        UserEntity db = checkActive(id);
        Preconditions.check(() -> !db.getPassword().equals(password(oldPwd, db.getSalt())), ErrorCode.OLD_PWD_NO_MATCH);
        update(UserEntity.builder()
                .id(db.getId())
                .password(password(newPwd, db.getSalt()))
                .pwdModifyTime(DateUtils.now())
                .build());
    }

    // endregion

}
